using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;

namespace Ns.Data.Entity
{
    /// <summary>
    /// Represents a type supporting change tracking, change notification, generic identifier and generic auditing of basic information.    
    /// </summary>
    /// <typeparam name="TId">The type of the <see cref="IdEntity&lt;TId&gt;.Id"/> property.</typeparam>
    /// <typeparam name="TCreatedBy">The type of the <see cref="CreatedBy"/> property.</typeparam>
    /// <typeparam name="TCreatedOn">The type of the <see cref="CreatedOn"/> property.</typeparam>
    /// <typeparam name="TUpdatedBy">The type of the <see cref="UpdatedBy"/> property.</typeparam>
    /// <typeparam name="TUpdatedOn">The type of the <see cref="UpdatedOn"/> property.</typeparam>
    /// <remarks>Storage members are marked as protected in order to enable mapping with LinqToSql.</remarks>
#if !SILVERLIGHT
    [Serializable]
#endif
    [DataContract(Name = "AuditEntityOf{0}")]
    public abstract class AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn> 
        : PersistEntity
        , IIdEntity<TId>
        , IAuditEntity<TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn>
        , IEquatable<AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn>>
    {
        /// <summary>
        /// Gets or sets a value indicating whether the <see cref="ChangingUpdatedBy"/> 
        /// and <see cref="ChangingUpdatedOn"/> events are raised 
        /// for new <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/>.
        /// </summary>
        /// <value>
        /// <b>true</b> if the <see cref="ChangingUpdatedBy"/> 
        /// and <see cref="ChangingUpdatedOn"/> events are raised 
        /// for new <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/>; otherwise, <b>false</b>.
        /// </value>
        /// <remarks>The <see cref="ChangeTracking"/> must also be <see cref="ChangeTracking.Enabled"/>.</remarks>
        public static bool RaiseChangingUpdatedEventsOnNew { get; set; }

        #region IIdEntity<TId> Members

        /// <ignore/>
        protected TId _id;

        /// <summary>
        /// Gets or sets the entity identifier.
        /// </summary>
        /// <value>The entity Id.</value>
        [DataMember(EmitDefaultValue = false)]
        public virtual TId Id
        {
            get { return _id; }
            set { _id = value; }
        }
        #endregion

        #region IEntityAudit Members
        
        /// <ignore/>
        protected TCreatedBy _createdBy;

        /// <summary>
        /// Gets or sets the user who has created the current entity.
        /// </summary>
        /// <value>The creator.</value>
        [DataMember(EmitDefaultValue = false)]
        public virtual TCreatedBy CreatedBy
        {
            get { return _createdBy; }
            set { _createdBy = value; }
        }

        /// <ignore/>
        protected TCreatedOn _createdOn;

        /// <summary>
        /// Gets or sets the creation date and time of the current entity.
        /// </summary>
        /// <value>The creation date and time.</value>
        [DataMember(EmitDefaultValue = false)]
        public virtual TCreatedOn CreatedOn
        {
            get { return _createdOn; }
            set { _createdOn = value; }
        }

        /// <ignore/>
        protected TUpdatedBy _updatedBy;

        /// <summary>
        /// Gets or sets the last user who has updated the current entity.
        /// </summary>
        /// <value>The last updater.</value>        
        [DataMember(EmitDefaultValue = false)]
        public virtual TUpdatedBy UpdatedBy
        {
            get { return _updatedBy; }
            set { _updatedBy = value; }
        }

        /// <ignore/>
        protected TUpdatedOn _updatedOn;

        /// <summary>
        /// Gets or sets the last update date and time of the current entity.
        /// </summary>
        /// <value>The last update date and time.</value>
        [DataMember(EmitDefaultValue = false)]
        public virtual TUpdatedOn UpdatedOn
        {
            get { return _updatedOn; }
            set { _updatedOn = value; }
        }
        
        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Touches this instance if no changes were made before.
        /// </summary>
        public override void Touch()
        {
            base.Touch();

            if (PersistChange == PersistChange.Update)
            {
                if (ChangingUpdatedBy != null)
                {
                    var args = new ChangingEventArgs<TUpdatedBy>();
                    ChangingUpdatedBy(this, args);
                    _updatedBy = args.Result;
                }

                if (ChangingUpdatedOn != null)
                {
                    var args = new ChangingEventArgs<TUpdatedOn>();
                    ChangingUpdatedOn(this, args);
                    _updatedOn = args.Result;
                }
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> class 
        /// with the <see cref="PersistEntity.CurrentPersistChange"/> 
        /// and according to the <see cref="PersistEntity.CurrentChangeTracking"/>.
        /// </summary>
        public AuditEntity()
            : this(PersistEntity.CurrentPersistChange
            ?? ((CurrentChangeTracking == ChangeTracking.Enabled)
                ? PersistChange.Insert
                : PersistChange.None)
            , PersistEntity.CurrentChangeTracking)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> class 
        /// with the <see cref="PersistEntity.CurrentPersistChange"/> 
        /// and according to the specified <see cref="ChangeTracking"/>.
        /// </summary>
        /// <param name="changeTracking">The change tracking.</param>
        public AuditEntity(ChangeTracking changeTracking)
            : this(PersistEntity.CurrentPersistChange
            ?? ((changeTracking == ChangeTracking.Enabled)
                ? PersistChange.Insert
                : PersistChange.None)
            , changeTracking)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> class 
        /// with the specified <see cref="PersistChange"/> 
        /// and according to the <see cref="PersistEntity.CurrentChangeTracking"/>.
        /// </summary>
        /// <param name="persistChange">The persist change.</param>
        public AuditEntity(PersistChange persistChange)
            : this(persistChange, PersistEntity.CurrentChangeTracking)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> class 
        /// with the specified <see cref="PersistChange"/> 
        /// and according to the specified <see cref="ChangeTracking"/>.
        /// </summary>
        /// <param name="persistChange">The persist change.</param>
        /// <param name="changeTracking">The change tracking.</param>
        public AuditEntity(PersistChange persistChange, ChangeTracking changeTracking)
            : base(persistChange)
        {
            if (changeTracking == ChangeTracking.Enabled)
            {
                if (ChangingId != null)
                {
                    var args = new ChangingEventArgs<TId>();
                    ChangingId(this, args);
                    _id = args.Result;
                }

                if (ChangingCreatedBy != null)
                {
                    var e = new ChangingEventArgs<TCreatedBy>();
                    ChangingCreatedBy(this, e);
                    _createdBy = e.Result;
                }

                if (ChangingCreatedOn != null)
                {
                    var e = new ChangingEventArgs<TCreatedOn>();
                    ChangingCreatedOn(this, e);
                    _createdOn = e.Result;
                }

                if (RaiseChangingUpdatedEventsOnNew)
                {
                    if (ChangingUpdatedBy != null)
                    {
                        var e = new ChangingEventArgs<TUpdatedBy>();
                        ChangingUpdatedBy(this, e);
                        _updatedBy = e.Result;
                    }

                    if (ChangingUpdatedOn != null)
                    {
                        var e = new ChangingEventArgs<TUpdatedOn>();
                        ChangingUpdatedOn(this, e);
                        _updatedOn = e.Result;
                    }
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> class
        /// copied from the specified <paramref name="entity"/>.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <remarks>This constructor ignores any <see cref="ChangeTrackingScope"/> and <see cref="PersistChangeScope"/>.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="entity"/> is null.</exception>
        protected AuditEntity(AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn> entity)
            : base(entity)
        {
            this._createdBy = entity._createdBy;
            this._createdOn = entity._createdOn;
            this._id = entity._id;
            this._updatedBy = entity._updatedBy;
            this._updatedOn = entity._updatedOn;
        }

        #endregion

        #region Overriden Members

        /// <summary>
        /// Converts the value of the current <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> object to its equivalent string representation. 
        /// </summary>
        /// <returns>A string representation of the value of the current <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;"/> object.</returns>
        public override string ToString()
        {
            return string.Format("{0}, Id = {1}, CreatedBy = {2}, CreatedOn = {3}, UpdatedBy = {4}, UpdatedOn = {5}"
                , base.ToString()
                , Id
                , CreatedBy
                , CreatedOn
                , UpdatedBy
                , UpdatedOn);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            int hash = 0x4043ed47;
            hash = (hash * -1521134295) + PersistChange.GetHashCode();
            hash = (hash * -1521134295) + (object.Equals(Id, default(TId)) ? 0 : Id.GetHashCode());
            hash = (hash * -1521134295) + (object.Equals(CreatedBy, default(TCreatedBy)) ? 0 : CreatedBy.GetHashCode());
            hash = (hash * -1521134295) + (object.Equals(CreatedOn, default(TCreatedOn)) ? 0 : CreatedOn.GetHashCode());
            hash = (hash * -1521134295) + (object.Equals(UpdatedBy, default(TUpdatedBy)) ? 0 : UpdatedBy.GetHashCode());
            hash = (hash * -1521134295) + (object.Equals(UpdatedOn, default(TUpdatedOn)) ? 0 : UpdatedOn.GetHashCode());
            return hash;
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            return (obj is AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn>)
                ? Equals((AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn>)obj)
                : false;
        }

        #endregion        

        #region IEquatable<AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn>> Members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <remarks>To compare uniquely on the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;.Id"/> property consider using the <see cref="Ns.Collections.Generic.MemberEqualityComparer&lt;T&gt;"/>.</remarks>
        /// <example>
        /// In the example below, comparaison of the entities is perfomed uniquely on the <see cref="AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;.Id"/> property.
        /// <code>        
        ///     var distinct = entities.Distinct(MemberEqualityComparer&lt;AuditEntity&lt;TId,TCreatedBy,TCreatedOn,TUpdatedBy,TUpdatedOn&gt;&gt;.Default(e => e.Id));
        /// </code>
        /// </example>
        public bool Equals(AuditEntity<TId, TCreatedBy, TCreatedOn, TUpdatedBy, TUpdatedOn> other)
        {
            return ReferenceEquals(this, other) ||
                   (other != null
                    && object.Equals(Id, other.Id)
                    && object.Equals(PersistChange, other.PersistChange)
                    && object.Equals(CreatedBy, other.CreatedBy)
                    && object.Equals(CreatedOn, other.CreatedOn)
                    && object.Equals(UpdatedBy, other.UpdatedBy)
                    && object.Equals(UpdatedOn, other.UpdatedOn));
        }

        #endregion

        #region Events

        /// <summary>
        /// Clears handlers of the <see cref="ChangingId"/>, 
        /// <see cref="ChangingCreatedBy"/>, <see cref="ChangingCreatedOn"/>,
        /// <see cref="ChangingUpdatedBy"/>, <see cref="ChangingUpdatedOn"/> events.
        /// </summary>
        public static void ClearEventHandlers()
        {
            ChangingId = null;
            ChangingCreatedBy = null;
            ChangingCreatedOn = null;
            ChangingUpdatedBy = null;
            ChangingUpdatedOn = null;
        }        

        /// <summary>
        /// Occurs when the <see cref="Id"/> gonna change.
        /// </summary>
        /// <remarks>This event enables centralization of generation.</remarks>
        public static event EventHandler<ChangingEventArgs<TId>> ChangingId;

        /// <summary>
        /// Occurs when the <see cref="CreatedBy"/> gonna change.
        /// </summary>
        /// <remarks>This event enables centralization of generation.</remarks>
        public static event EventHandler<ChangingEventArgs<TCreatedBy>> ChangingCreatedBy;

        /// <summary>
        /// Occurs when the <see cref="CreatedOn"/> gonna change.
        /// </summary>
        /// <remarks>This event enables centralization of generation.</remarks>
        public static event EventHandler<ChangingEventArgs<TCreatedOn>> ChangingCreatedOn;

        /// <summary>
        /// Occurs when the <see cref="UpdatedBy"/> gonna change.
        /// </summary>
        /// <remarks>This event enables centralization of generation.</remarks>
        public static event EventHandler<ChangingEventArgs<TUpdatedBy>> ChangingUpdatedBy;

        /// <summary>
        /// Occurs when the <see cref="UpdatedOn"/> gonna change.
        /// </summary>
        /// <remarks>This event enables centralization of generation.</remarks>
        public static event EventHandler<ChangingEventArgs<TUpdatedOn>> ChangingUpdatedOn;

        #endregion
    }
}